﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Collections.Specialized;

namespace NLite.Reflection
{
    /// <summary>
    /// 
    /// </summary>
    public static class TypeHelper
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="enumerableType"></param>
        /// <returns></returns>
        public static Type GetElementType(Type enumerableType)
        {
            return GetElementType(enumerableType, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="enumType"></param>
        /// <returns></returns>
        public static Type GetEnumType(Type enumType)
        {
            if (enumType == null)
                return null;
            if (enumType.IsNullable())
                enumType = enumType.GetGenericArguments()[0];
            if (enumType.IsEnum)
                return enumType;
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool CanConvertToEnum(Type type)
        {
            if (TypeHelper.IsEnumType(type))
                return true;

            if (type.IsNullable())
                type = Nullable.GetUnderlyingType(type);

            return
                                TypeHelper.IsEnumType(type)
                                || Types.String == type
                                || Types.Decimal == type
                                || Types.Double == type
                                || Types.Int16 == type
                                || Types.Int32 == type
                                || Types.Int64 == type
                                || Types.Single == type
                                || Types.UInt16 == type
                                || Types.UInt32 == type
                                || Types.UInt64 == type;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="enumerableType"></param>
        /// <param name="enumerable"></param>
        /// <returns></returns>
        public static Type GetElementType(Type enumerableType, IEnumerable enumerable)
        {
            if (enumerableType.HasElementType)
            {
                return enumerableType.GetElementType();
            }
            if (enumerableType.IsGenericType && enumerableType.GetGenericTypeDefinition().Equals(Types.IEnumerableofT))
            {
                return enumerableType.GetGenericArguments()[0];
            }
            Type iEnumerableType = GetIEnumerableType(enumerableType);
            if (iEnumerableType != null)
            {
                return iEnumerableType.GetGenericArguments()[0];
            }
            if (!Types.IEnumerable.IsAssignableFrom(enumerableType))
            {
                throw new ArgumentException(string.Format("Unable to find the element type for type '{0}'.", enumerableType), "enumerableType");
            }
            if (enumerable != null)
            {
                object obj2 = enumerable.Cast<object>().FirstOrDefault<object>();
                if (obj2 != null)
                {
                    return obj2.GetType();
                }
            }
            return typeof(object);
        }

        private static Type GetIEnumerableType(Type enumerableType)
        {
            try
            {
                return enumerableType.GetInterface("IEnumerable`1", false);
            }
            catch (AmbiguousMatchException)
            {
                if (enumerableType.BaseType != typeof(object))
                {
                    return GetIEnumerableType(enumerableType.BaseType);
                }
                return null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsGuidType(Type type)
        {
            if (type == null )
                return false;
            if (type == Types.Guid)
                return true;

            if (type.IsNullable()
                && type.GetGenericArguments()[0] == typeof(Guid))
                return true;

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsEnumType(Type type)
        {
            return GetEnumType(type) != null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsFlagsEnum(Type type)
        {
            return TypeHelper.IsEnumType(type) && type.GetCustomAttributes(typeof(FlagsAttribute), false).Any();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsGenericDictionaryType(this Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
                return true;

            var genericInterfaces = type.GetInterfaces().Where(t => t.IsGenericType);
            var baseDefinitions = genericInterfaces.Select(t => t.GetGenericTypeDefinition());
            return baseDefinitions.Any(t => t == typeof(IDictionary<,>));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsIDictionaryType(this Type type)
        {
            return Types.IDictionary.IsAssignableFrom(type);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsDictionaryType(this Type type)
        {
            return IsIDictionaryType(type) || IsGenericDictionaryType(type);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Type GetGenericDictionaryType(this Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
                return type;

            var genericInterfaces = type.GetInterfaces().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>));
            return genericInterfaces.FirstOrDefault();
        }
    }

   
}


namespace NLite
{
    /// <summary>
    /// 
    /// </summary>
    public static class Types
    {
        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Object = typeof(Object);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Type = typeof(Type);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type String = typeof(String);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Char = typeof(Char);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Boolean = typeof(Boolean);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Byte = typeof(Byte);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type SByte = typeof(SByte);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Int16 = typeof(Int16);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type UInt16 = typeof(UInt16);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Int32 = typeof(Int32);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type UInt32 = typeof(UInt32);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Int64 = typeof(Int64);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type UInt64 = typeof(UInt64);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Double = typeof(Double);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Single = typeof(Single);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Decimal = typeof(Decimal);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Guid = typeof(Guid);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Nullable = typeof(Nullable<>);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type ValueType = typeof(ValueType);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Void = typeof(void);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type DBNull = typeof(DBNull);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type Delegate = typeof(Delegate);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type ByteEnumerable = typeof(IEnumerable<Byte>);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IEnumerableofT = typeof(System.Collections.Generic.IEnumerable<>);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IEnumerable = typeof(System.Collections.IEnumerable);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IListSource = typeof(System.ComponentModel.IListSource);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IDictionary = typeof(System.Collections.IDictionary);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IDictionaryOfT = typeof(IDictionary<,>);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type StringDictionary = typeof(StringDictionary);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type NameValueCollection = typeof(NameValueCollection);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IDataReader = typeof(System.Data.IDataReader);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type DataTable = typeof(System.Data.DataTable);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type DataRow = typeof(System.Data.DataRow);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IDictionaryOfStringAndObject = typeof(IDictionary<string, object>);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Type IDictionaryOfStringAndString = typeof(IDictionary<string, string>);

    }
}